#include "iConfig.hpp"
#include <glf/core/readWriteSpinLock.h>
#include <glf/core/timer.h>

#if __APPLE__
	#include "synchronization_posix_.hpp"
#else
	#include GLFi_HEADER_API(synchronization, GLF_API_THREAD)
#endif


// Property: - Recursive
//			 - Busy-Wait
//			 - Unfair (writer get precedence)
//			 - Shared

#undef Yield

namespace glf
{

ReadWriteSpinLock::ReadWriteSpinLock()
	: Owner(0)
	, WriterCount(0)
{
	GLF_STATIC_ASSERT(!(GLF_MAX_THREADS % sizeof(int)));

	memset(LockArray, 0, sizeof(LockArray));
}

bool ReadWriteSpinLock::hasReadLock() const
{
	int id = GetCurrentSequentialThreadId();
	GLF_ASSERT(id < GLF_MAX_THREADS);

	const uchar& lock = LockArray[id];
	return lock > 0 || id == Owner;
}

bool ReadWriteSpinLock::hasWriteLock() const
{
	int id = GetCurrentSequentialThreadId();
	GLF_ASSERT(id < GLF_MAX_THREADS);

	return id == Owner;
}

int
ReadWriteSpinLock::readLockImpl(unsigned int waitTime)
{
	int id = GetCurrentSequentialThreadId();
	uchar& lock = LockArray[id];

	//
	// Recursive test - Did we already lock ?
	// (no need for thread safety - if we are in "recursive mode", everything happens on the same thread/object)
	//

	// Already own a read lock or a write lock ?
	if(lock > 0 || id == Owner)
	{
		// Yes, increment lock count
		++lock;
		GLF_ASSERT(lock != 0);	// Overflow
		return 1;
	}

	//
	// Try to acquire the lock
	//

	int64 timer = 0;
	for(;;)
	{
		// Verify if there is a writer lock
		if(Owner)
		{
			// Yes :(
			Thread::Sleep(0);

			// Verify timer
			if(!timer)
			{
				timer = GetMilliseconds();
			}
			if((GetMilliseconds() - timer) > waitTime)
			{
				return 0;
			}

			// Retry
			continue;
		}

		// Ask for a reader lock
		// MT-NOTE: on windows, this code takes the code with mutex.... and I think it could be refactor with membar.
		AtomicIncrement(lock);	// Must be atomic, otherwise writeLock may see it delayed...
		GLF_ASSERT(lock != 0);	// Overflow

		// Verify that the writer lock wasn't acquired during that time
		if(Owner)
		{
			// Someone acquired the lock, release our reader lock and restart
			--lock; // No need for atomic; we don''t care if it's delayed.

			// Verify timer
			if(!timer)
			{
				timer = GetMilliseconds();
			}
			if((GetMilliseconds() - timer) > waitTime)
			{
				return 0;
			}

			// Retry
			continue;
		}

		// We acquired the lock !
		break;
	}

	//
	// Lock acquired !
	//

	return 1;
}

int
ReadWriteSpinLock::readUnlock()
{
	int id = GetCurrentSequentialThreadId();
	uchar& lock = LockArray[id];

	// Release the lock
	//
	// No need for atomicity; this code will run faster when there is no contention and slower when there
	// is contention. Since this lock is unfair, we assume contention is kept to a minimal.
	--lock;

	return 1;
}

int
ReadWriteSpinLock::writeLockImpl(unsigned int waitTime)
{
	int id = glf::GetCurrentSequentialThreadId();

	//
	// Recursive test - Are we the owner ?
	// (no need for thread safety - if we are in "recursive mode", everything happens on the same thread/object)
	//

	if(Owner == id)
	{
		// Yes, increment lock count
		++WriterCount;
		return 1;
	}

	//
	// Promote lock if needed
	//

	PromoteCount[id] = LockArray[id];
	LockArray[id] = 0;

	//
	// Try to acquire the lock
	//

	int64 timer = 0;
	while(AtomicCompareAndSwap(Owner, id, 0) != 0)
	{
		// wait for lock, and spin !
		Thread::Sleep(0);

		// Verify timer
		if(!timer)
		{
			timer = GetMilliseconds();
		}
		if((GetMilliseconds() - timer) > waitTime)
		{
			// Demote lock if needed
			LockArray[id] = PromoteCount[id];
			return 0;
		}
	}

	//
	// lock acquired !
	//

	// increase recursive the count
	++WriterCount;

	//
	// Drain all reader
	//

	for(int i = 0; i < GLF_MAX_THREADS; i += sizeof(int))
	{
		// If the lock is aquired, restart the drain
		if(*reinterpret_cast<int*>(&LockArray[i]))
		{
			i = -(int)sizeof(int);	// -sizeof(int), since the end of loop will do += sizeof(int) to reset it to 0
			Thread::Sleep(0);

			// Verify timer
			if(!timer)
			{
				timer = GetMilliseconds();
			}
			if((GetMilliseconds() - timer) > waitTime)
			{
				writeUnlock();
				return 0;
			}
		}
	}

	return 1;
}

int
ReadWriteSpinLock::writeUnlock()
{
	// Release the lock only if all recursive lock are unlocked
	if(!--WriterCount)
	{
		int id = glf::GetCurrentSequentialThreadId();

		//
		// Demote lock if needed
		//

		LockArray[id] = PromoteCount[id];

		//
		// Release lock
		//

		Owner = 0;
	}

	return 1;
}

int
ReadWriteSpinLock::getOwner() const
{ 
	return Owner;
}

} //namespace glf
